﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Data;

namespace Titan.Reflection
{
    internal class Binder
    {
        public const ParameterDirection UnknownDirection = (ParameterDirection)(-1);

        public ParameterDirection GetDirection(ParameterInfo parameter)
        {
            if (parameter.IsIn && (!parameter.ParameterType.IsByRef))
                return ParameterDirection.Input;

            if (parameter.IsOut)
                return ParameterDirection.Output;

            if (parameter.IsIn && parameter.ParameterType.IsByRef)
                return ParameterDirection.InputOutput;

            if (parameter.IsRetval)
                return ParameterDirection.ReturnValue;

            return ParameterDirection.Input;
        }

        public ConstructorInfo SelectConstructor(Type type, BindingFlags bindingAttr, object[] args, ParameterDirection[] directions)
        {
            List<ConstructorInfo> match1, match2;

            ConstructorInfo[] constructors = type.GetConstructors(bindingAttr);

            if (0 == constructors.Length)
                return null;

            if (null != directions)
            {
                match1 = new List<ConstructorInfo>();

                foreach (ConstructorInfo ctor in constructors)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == directions.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (directions[i] != UnknownDirection && directions[i] != GetDirection(parameters[i]))
                            {
                                matched = false;
                                break;
                            }
                        }
                        if (matched)
                            match1.Add(ctor);
                    }
                }
            }
            else
            {
                match1 = new List<ConstructorInfo>(constructors);
            }

            if (0 == match1.Count)
                return null;

            match2 = new List<ConstructorInfo>();

            if (null == args || 0 == args.Length)
            {
                foreach (ConstructorInfo ctor in match1)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == 0)
                    {
                        match2.Add(ctor);
                    }
                }
            }
            else
            {
                foreach (ConstructorInfo ctor in match1)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == args.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (null == args[i])
                            {
                                if (parameters[i].ParameterType.IsValueType)
                                {
                                    matched = false;
                                    break;
                                }
                            }
                            else
                            {
                                Type argType = args[i].GetType();
                                Type paramType = parameters[i].ParameterType;

                                if (paramType != argType && (!paramType.IsAssignableFrom(argType)))
                                {
                                    matched = false;
                                    break;
                                }
                            }
                        }
                        if (matched)
                            match2.Add(ctor);
                    }
                }
            }

            if (match2.Count > 0)
                return match2[0];

            return null;
        }

        public ConstructorInfo SelectConstructor(Type type, BindingFlags bindingAttr, Type[] parameterTypes, ParameterDirection[] directions)
        {
            List<ConstructorInfo> match1, match2;

            ConstructorInfo[] constructors = type.GetConstructors(bindingAttr);

            if (0 == constructors.Length)
                return null;

            if (null != directions)
            {
                match1 = new List<ConstructorInfo>();

                foreach (ConstructorInfo ctor in constructors)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == directions.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (directions[i] != UnknownDirection && directions[i] != GetDirection(parameters[i]))
                            {
                                matched = false;
                                break;
                            }
                        }
                        if (matched)
                            match1.Add(ctor);
                    }
                }
            }
            else
            {
                match1 = new List<ConstructorInfo>(constructors);
            }

            if (0 == match1.Count)
                return null;

            if (null == parameterTypes)
                return match1[0];

            match2 = new List<ConstructorInfo>();

            if (0 == parameterTypes.Length)
            {
                foreach (ConstructorInfo ctor in match1)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == 0)
                    {
                        match2.Add(ctor);
                    }
                }
            }
            else
            {
                foreach (ConstructorInfo ctor in match1)
                {
                    ParameterInfo[] parameters = ctor.GetParameters();
                    if (parameters.Length == parameterTypes.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (null != parameterTypes[i])
                            {
                                Type argType = parameterTypes[i];
                                Type paramType = parameters[i].ParameterType;

                                if (paramType != argType && (!paramType.IsAssignableFrom(argType)))
                                {
                                    matched = false;
                                    break;
                                }
                            }
                        }
                        if (matched)
                            match2.Add(ctor);
                    }
                }
            }

            if (match2.Count > 0)
                return match2[0];

            return null;
        }

        public MethodInfo SelectMethod(Type type, string methodName, BindingFlags bindingAttr, object[] args, ParameterDirection[] directions)
        {
            List<MethodInfo> match1, match2, match3;

            MethodInfo[] methods = type.GetMethods(bindingAttr);
            bool ignoreCase = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase;
            match1 = new List<MethodInfo>();

            foreach (MethodInfo method in methods)
            {
                if (string.Compare(method.Name, methodName, ignoreCase) == 0)
                {
                    match1.Add(method);
                }
            }

            if (0 == match1.Count)
                return null;

            if (null != directions)
            {
                match2 = new List<MethodInfo>();

                foreach (MethodInfo method in match1)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == directions.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (directions[i] != UnknownDirection && directions[i] != GetDirection(parameters[i]))
                            {
                                matched = false;
                                break;
                            }
                        }
                        if (matched)
                            match2.Add(method);
                    }
                }
            }
            else
            {
                match2 = match1;
            }

            if (0 == match2.Count)
                return null;

            match3 = new List<MethodInfo>();

            if (null == args || 0 == args.Length)
            {
                foreach (MethodInfo method in match2)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == 0)
                    {
                        match3.Add(method);
                    }
                }
            }
            else
            {
                foreach (MethodInfo method in match2)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == args.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (null == args[i])
                            {
                                if (parameters[i].ParameterType.IsValueType)
                                {
                                    matched = false;
                                    break;
                                }
                            }
                            else
                            {
                                Type argType = args[i].GetType();
                                Type paramType = parameters[i].ParameterType;

                                if (paramType != argType && (!paramType.IsAssignableFrom(argType)))
                                {
                                    matched = false;
                                    break;
                                }
                            }
                        }
                        if (matched)
                            match3.Add(method);
                    }
                }
            }

            if (match3.Count > 0)
                return match3[0];

            return null;
        }

        public MethodInfo SelectMethod(Type type, string methodName, BindingFlags bindingAttr, Type[] parameterTypes, ParameterDirection[] directions)
        {
            List<MethodInfo> match1, match2, match3;

            MethodInfo[] methods = type.GetMethods(bindingAttr);
            bool ignoreCase = (bindingAttr & BindingFlags.IgnoreCase) == BindingFlags.IgnoreCase;
            match1 = new List<MethodInfo>();

            foreach (MethodInfo method in methods)
            {
                if (string.Compare(method.Name, methodName, ignoreCase) == 0)
                {
                    match1.Add(method);
                }
            }

            if (0 == match1.Count)
                return null;

            if (null != directions)
            {
                match2 = new List<MethodInfo>();

                foreach (MethodInfo method in match1)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == directions.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (directions[i] != UnknownDirection && directions[i] != GetDirection(parameters[i]))
                            {
                                matched = false;
                                break;
                            }
                        }
                        if (matched)
                            match2.Add(method);
                    }
                }
            }
            else
            {
                match2 = match1;
            }

            if (0 == match2.Count)
                return null;

            if (null == parameterTypes)
                return match2[0];

            match3 = new List<MethodInfo>();

            if (0 == parameterTypes.Length)
            {
                foreach (MethodInfo method in match2)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == 0)
                    {
                        match3.Add(method);
                    }
                }
            }
            else
            {
                foreach (MethodInfo method in match2)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    if (parameters.Length == parameterTypes.Length)
                    {
                        bool matched = true;
                        for (int i = 0; i < parameters.Length; i++)
                        {
                            if (null != parameterTypes[i])
                            {
                                Type argType = parameterTypes[i];
                                Type paramType = parameters[i].ParameterType;

                                if (paramType != argType && (!paramType.IsAssignableFrom(argType)))
                                {
                                    matched = false;
                                    break;
                                }
                            }
                        }
                        if (matched)
                            match3.Add(method);
                    }
                }
            }

            if (match3.Count > 0)
                return match3[0];

            return null;
        }

        public static readonly Binder Default = new Binder();






        /*
        public override FieldInfo BindToField(BindingFlags bindingAttr, FieldInfo[] match, object value, CultureInfo culture)
        {
            
            throw new Exception("The method or operation is not implemented.");
        }
        */

        /*
        public MethodBase BindToMethod(BindingFlags bindingAttr, MethodBase[] match, ref object[] args, ParameterDirection[] directions, string[] names, out object state)
        {
            state = null;

            if (null == match || 0 == match.Length)
                return null;

            foreach (MethodBase method in match) {
                if ((bindingAttr & BindingFlags.IgnoreCase) == 0) {
                    if (string.Compare(method.Name, name) == 0)
                }
            }
            return null;
        }
        */

        /*
        public override object ChangeType(object value, Type type, CultureInfo culture)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        

        public override void ReorderArgumentArray(ref object[] args, object state)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override MethodBase SelectMethod(BindingFlags bindingAttr, MethodBase[] match, Type[] types, ParameterModifier[] modifiers)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override PropertyInfo SelectProperty(BindingFlags bindingAttr, PropertyInfo[] match, Type returnType, Type[] indexes, ParameterModifier[] modifiers)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        */
    }
}
